﻿using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using Mono.Cecil;
using WeavingCommon;

namespace NotifyPropertyWeaverMsBuildTask
{
    [Export]
    public class PropertyDataWalker
    {
        TypeNodeBuilder typeNodeBuilder;
        NotifyPropertyDataAttributeReader notifyPropertyDataAttributeReader;
        Logger logger;
        WeavingTask weavingTask;

        [ImportingConstructor]
        public PropertyDataWalker(TypeNodeBuilder typeNodeBuilder, NotifyPropertyDataAttributeReader notifyPropertyDataAttributeReader, Logger logger, WeavingTask weavingTask)
        {
            this.typeNodeBuilder = typeNodeBuilder;
            this.notifyPropertyDataAttributeReader = notifyPropertyDataAttributeReader;
            this.logger = logger;
            this.weavingTask = weavingTask;
        }

        void Process(List<TypeNode> notifyNodes)
        {
            foreach (var node in notifyNodes)
            {
                foreach (var property in node.TypeDefinition.Properties)
                {
                    var containsAttribute = property.CustomAttributes.ContainsAttribute("NotifyPropertyAttribute");
                    if (property.CustomAttributes.ContainsAttribute("DoNotNotifyAttribute"))
                    {
                        continue;
                    }
                    if (property.SetMethod == null)
                    {
                        if (containsAttribute)
                        {
                            logger.LogMessage(string.Format("\t\t'{0}' skipped because could not find a field set in the property set code. Are you missing code to set the field?", property.GetName()));
                        }
                        continue;
                    }

                    if (!containsAttribute && !node.ShouldNotifyForAllInType)
                    {
                        continue;
                    }
                    GetPropertyData(property, node);
                }
                Process(node.Nodes);
            }
        }

        void GetPropertyData(PropertyDefinition propertyDefinition, TypeNode node)
        {
            var notifyPropertyData = notifyPropertyDataAttributeReader.Read(propertyDefinition, node.AllProperties);
            var dependenciesForProperty = node.PropertyDependencies.Where(x => x.WhenPropertyIsSet == propertyDefinition).Select(x => x.ShouldAlsoNotifyFor);

            var backingFieldReference = node.Mappings.First(x => x.PropertyDefinition == propertyDefinition).FieldDefinition;
            if (notifyPropertyData == null)
            {
                if (node.EventInvoker == null)
                {
                    return;
                }
                node.PropertyDatas.Add(new PropertyData
                           {
                               CheckForEquality = weavingTask.CheckForEquality,
                               BackingFieldReference = backingFieldReference,
                               NotificationAddedDirectly = false,
                               PropertyDefinition = propertyDefinition,
                               AlsoNotifyFor = dependenciesForProperty.ToList()
                           });
                return;
            }

            if (node.EventInvoker == null)
            {
                throw new WeavingException(string.Format(
                    @"Could not find field for PropertyChanged event on type '{0}'.
Looked for 'PropertyChanged', 'propertyChanged', '_PropertyChanged' and '_propertyChanged'.
The most likely cause is that you have implemented a custom event accessor for the PropertyChanged event and have called the PropertyChangedEventHandler something stupid.", node.TypeDefinition.FullName));
            }
            node.PropertyDatas.Add(new PropertyData
                       {
                           CheckForEquality = notifyPropertyData.CheckForEquality.GetValueOrDefault(weavingTask.CheckForEquality),
                           BackingFieldReference = backingFieldReference,
                           NotificationAddedDirectly = true,
                           PropertyDefinition = propertyDefinition,
                           AlsoNotifyFor = notifyPropertyData.AlsoNotifyFor.Union(dependenciesForProperty).ToList()
                       });
        }

        public void Execute()
        {
            Process(typeNodeBuilder.NotifyNodes);
        }
    }
}